package cn.codergege.gradle.plugin.kotlin

import cn.codergege.gradle.plugin.generateSourceCode
import org.gradle.api.tasks.TaskAction

open class KtBootWebBasicStage: KtBootBasicStage() {

    @TaskAction
    override fun run() {
        super.run()
        // tag::main/kotlin[]
        // 生成 spring boot main
        generateSourceCode(mainKtDir, packageDir, mainFileName, codeAppMain)
        // cn.codergege.demo.config.DruidDataSourceConfig
        generateSourceCode(mainKtDir, configPackageDir, druidDataSourceConfigFileName, codeDruidDataSourceConfig)
        // cn.codergege.demo.config.RedisConfig
        generateSourceCode(mainKtDir, configPackageDir, redisConfigFileName, codeRedisConfig)
        // cn.codergege.demo.controller.HelloWorldController
        generateSourceCode(mainKtDir, controllerPackageDir, helloControllerFileName, codeHelloController)
        // cn.codergege.demo.dao.PersonRepository
//        generateSourceCode(mainKtDir, daoPackageDir, personRepoFileName, codePersonRepo)
        // cn.codergege.demo.dev.DbInit
//        generateSourceCode(mainKtDir, devPackageDir, dbInitFileName, codeDbInit)
        // cn.codergege.demo.entity.Person
//        generateSourceCode(mainKtDir, entityPackageDir, personFileName, codePerson)
        // cn.codergege.demo.service.PersonService
//        generateSourceCode(mainKtDir, servicePackageDir, personServiceFileName, codePersonService)
        // cn/codergege/demo/utils/utils.kt
//        generateSourceCode(mainKtDir, utilsPackageDir, utilsFileName, codeUtils)
        // end::main/kotlin[]

        // tag::main/resources[]
        // application.yml
        generateSourceCode(mainResourceDir, "", appYmlFileName, codeAppYml)
        // end::main/resources[]

        // tag::test/kotlin[]
        // cn.codergege.demo.${convertedProjectName}ApplicationTest
        generateSourceCode(testKtDir, packageDir, appTestFileName, codeAppTest)
        // cn.codergege.demo.bean.Greeter
        generateSourceCode(testKtDir, beanPackageDir, greeterFileName, codeGreeter)
        // cn.codergege.demo.controller.HelloControllerTest
        generateSourceCode(testKtDir, controllerPackageDir
            , helloControllerTestFileName
            , codeHelloControllerTest)
        // cn.codergege.demo.service.PersonServiceTest
        generateSourceCode(testKtDir, servicePackageDir
            , personServiceTestFileName, codePersonServiceTest)
        // end::test/kotlin[]
    }

    override var readmeMd = """# ${project.name} #

## Quik Start ##

```bash
gradle test
gradle
```
localhost:8888/hello
localhost:8888/hi
localhost:8888/druid

## 使用的技术 ##

* Kotlin
* Spring Boot
    + web
    + data-jpa
* 数据库(单数据源)
    + jpa
    + mysql, H2
    + druid
    + querydsl
* 前端
    + 不涉及前端, 返回的是 Json
* Test
    + spring boot test
    + Junit5

## Stage 插件 ##

使用 ktBootWebBasic 生成项目
`gradle ktBootWebBasic`

## 修改为 Oracle 数据库 ##

* 新建 lib 目录, 复制 ojdbc6.jar 到 lib, 刷新 gradle
* 修改 yml 配置文件, 主要需要修改以下内容
```yaml
url: jdbc:oracle:thin:@10.22.10.120:1521:orcl
username: base
password: base
driver-class-name: oracle.jdbc.OracleDriver
...
jpa:
    database: ORACLE
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.Oracle9iDialect
    hibernate:
      ddl-auto: update
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
```

## Todo ##

>  TODO: 验证?
>  TODO: 错误处理
"""

    // tag::main/kotlin[]
    override var codeAppMain = """package $packageName
import $packageName.dev.DbInit
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Profile
import org.springframework.core.env.Environment
import org.springframework.core.env.StandardEnvironment

@SpringBootApplication
class ${convertedProjectName}Application{
    @Autowired
    lateinit var env: Environment
    // for init dev data
    // 最好是在 CommandLineRunner 中直接通过 applicationContext 获取
    @Autowired
    var dbInit: DbInit? = null

    @Bean
    @Profile("dev")
    fun init() = CommandLineRunner {
        println("Active profiles from env: ${'$'}{env.activeProfiles[0]}")
        // Do it only the first time!
        // 如果是 dev 模式, 对数据库进行初始化
        if(env.activeProfiles[0] == "dev") dbInit?.initData()
    }
}

fun main(args: Array<String>) {
    println("Demo 开始运行...")
    runApplication<${convertedProjectName}Application>(*args) {
        val env = StandardEnvironment()
//        env.setActiveProfiles("dev")
        setEnvironment(env)
    }
}
"""

    // cn.codergege.demo.config.DruidDataSourceConfig
    override var configPackageDir = "$packageDir/config"
    override var druidDataSourceConfigFileName = "DruidDataSourceConfig.kt"
    override var codeDruidDataSourceConfig = """package $packageName.config

import com.alibaba.druid.pool.DruidDataSource
import com.alibaba.druid.support.http.StatViewServlet
import com.alibaba.druid.support.http.WebStatFilter
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.boot.web.servlet.ServletRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import java.util.*
import javax.sql.DataSource

@Configuration
@Profile("dev", "prod")
class DruidDataSourceConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    fun druidDataSource(): DataSource {
        return DruidDataSource()
    }

    // 配置 druid 监控
    // 1. 后台 servlet
    @Bean
    fun statViewServlet(): ServletRegistrationBean<StatViewServlet> {
        val bean = ServletRegistrationBean(StatViewServlet(), "/druid/*")

        val initParams = HashMap<String, String>()
        initParams["loginUsername"] = "admin"
        initParams["loginPassword"] = "111111"
        initParams["allow"] = ""//默认就是允许所有访问
//        initParams["deny"]
        bean.initParameters = initParams

        return bean
    }

    // 2. 用于监控的 filter
    @Bean
    fun webStatFilter(): FilterRegistrationBean<WebStatFilter> {
        val bean = FilterRegistrationBean<WebStatFilter>()
        bean.setFilter(WebStatFilter())

        val initParams = HashMap<String, String>()
        initParams["exclusions"] = "*.js,*.css,/druid/*"
        bean.initParameters = initParams
        bean.urlPatterns = Arrays.asList("/*")

        return bean
    }
}
"""
    // cn.codergege.demo.config.RedisConfig
    var redisConfigFileName = "RedisConfig.kt"
    var codeRedisConfig = """package $packageName.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
import java.net.UnknownHostException
import org.springframework.cache.annotation.EnableCaching

@EnableCaching
@Configuration
class RedisConfig {
    @Bean
    @Throws(UnknownHostException::class)
    fun jsonRedisTemplate(redisConnectionFactory: RedisConnectionFactory): RedisTemplate<Any, Any> {
        val template = RedisTemplate<Any, Any>()
        template.setConnectionFactory(redisConnectionFactory)
        template.setDefaultSerializer(Jackson2JsonRedisSerializer<Any>(Any::class.java))
        return template
    }

    //Todo: redis 使用 json 序列化
}
"""
    // cn.codergege.demo.controller.HelloController
    var controllerPackageDir = "$packageDir/controller"
    var helloControllerFileName = "HelloController.kt"
    var codeHelloController = """package $packageName.controller

import $packageName.entity.Person
import $packageName.service.PersonService
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController

@RestController
class HelloController {

    @Autowired
    lateinit var personService: PersonService

    private val log = LoggerFactory.getLogger(javaClass)

    @GetMapping(*arrayOf("/hello", "/hi"))
    fun hello(): Any? {
        log.info("Hello world! ...")
        val persons = personService.findPersonsByGender(1)
        persons?.forEach(::println)
        return persons
    }

    @GetMapping("/person/{id}")
    fun getPerson(@PathVariable("id") id: Int): Person {
        return personService.findPersonById(id)
    }
}
"""
    // cn.codergege.demo.dao.PersonRepository
    /*
    var daoPackageDir = "$packageDir/dao"
    var personRepoFileName = "PersonRepository.kt"
    var codePersonRepo = """package $packageName.dao

import $packageName.entity.Person
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.querydsl.QuerydslPredicateExecutor

interface PersonRepository: JpaRepository<Person, Int>
    , QuerydslPredicateExecutor<Person> {
    fun findByName(name: String): List<Person>?
}
"""
    // cn.codergege.demo.dev.DbInit
    var devPackageDir = "$packageDir/dev"
    var dbInitFileName = "DbInit.kt"
    var codeDbInit = """package $packageName.dev

import $packageName.dao.PersonRepository
import $packageName.entity.Person
import $packageName.utils.toDate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component

@Component
@Profile("dev")
class DbInit {
    @Autowired
    lateinit var personRepository: PersonRepository
//    @Value("\${'$'}{spring.profiles.active}")
//    lateinit var activeProfiles: String
//    @Value("\${'$'}{spring.jpa.hibernate.ddl-auto}")
//    lateinit var ddlAuto: String

    fun initData() {
        println("DEV mode: Initialize database with test data...")
        // 给 Person 表添加数据
        val persons = listOf<Person>(
            Person(null, "AA", 1, "1986-05-29".toDate(), "AA@163.com"),
            Person(null, "BB", 0, null, "AA@163.com")
        )
        personRepository.saveAll(persons)
    }
}
"""
    // cn.codergege.demo.entity.Person
    var entityPackageDir = "$packageDir/entity"
    var personFileName = "Person.kt"
    var codePerson = """package $packageName.entity

import java.util.*
import javax.persistence.*

@Entity
class Person(
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    val id: Long?,
    val name: String,
    val gender: Int?,
    @Temporal(TemporalType.DATE)
    val birthday: Date?,
    val email: String?,
    @Column(length = 3500)
    val description: String = "",
    val createTime: Date = Date()
) {
    override fun toString(): String {
        return "Person(id=${'$'}id, name='${'$'}name', gender=${'$'}gender, birthday=${'$'}birthday, email=${'$'}email, description='${'$'}description', createTime=${'$'}createTime)"
    }
}
"""
    // cn.codergege.demo.service.PersonService
    var servicePackageDir = "$packageDir/service"
    var personServiceFileName = "PersonService.kt"
    var codePersonService = """package $packageName.service

import $packageName.dao.PersonRepository
import $packageName.entity.Person
import $packageName.entity.QPerson.person
import com.querydsl.core.types.Predicate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

@Service
class PersonService {
   @Autowired
   lateinit var personRepository: PersonRepository

   fun findPersons(name: String): List<Person>? {
      return personRepository.findByName(name)
   }

   fun findPersonsByGender(gender: Int): Iterable<Person>? {
      val predicate: Predicate = person.gender.eq(gender)
      return personRepository.findAll(predicate)
   }
}
"""
    // cn/codergege/demo/utils/utils.kt
    var utilsPackageDir = "$packageDir/utils"
    var utilsFileName = "utils.kt"
    var codeUtils = """package $packageName.utils

import java.text.SimpleDateFormat
import java.util.*

fun String.toDate(): Date {
    return SimpleDateFormat("yyyy-MM-dd").parse(this)
}
"""
*/
    // end::main/kotlin[]

    // tag::main/resources[]
    // application.yml
    override var appYmlFileName = "application.yml"
    override var codeAppYml = """# 所有 profiles 中都生效
author: codergege
server:
  port: 8888
spring:
  # redis
  redis:
    host: 192.168.99.100
  profiles:
    #设置激活的 profile
    active: dev
---
# 默认 profiles
spring:
  profiles: default
greeting: This is default environment
---
# dev profiles
spring:
  profiles: dev
  # datasource
  datasource:
    # 数据源基本配置
    # url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
    url: jdbc:mysql://192.168.99.100:3306/demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    # druid 数据源配置
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    # 配置监控统计拦截的 filters, 去掉后监控界面 sql 无法统计, wall 用于防火墙
    # filters: stat,wall,log4j
    filters: stat,wall
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true; druid.stat.slowSqlMillis=500
  # jpa
  jpa:
    database: MYSQL
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
    hibernate:
      ddl-auto: update
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
greeting: This is development environment
---
# test profiles
spring:
  profiles: test
  # datasource: H2
  jpa:
    database: H2
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create-drop
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
greeting: This is test environment
---
# prod profiles
spring:
  profiles: prod
  # datasource
  datasource:
    # 数据源基本配置
    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    # druid 数据源配置
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    # 配置监控统计拦截的 filters, 去掉后监控界面 sql 无法统计, wall 用于防火墙
    # filters: stat,wall,log4j
    filters: stat,wall
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true; druid.stat.slowSqlMillis=500
  # jpa
  jpa:
    database: MYSQL
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
    hibernate:
      ddl-auto: none
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
greeting: This is production environment
"""
    // end::main/resources[]
    // tag::test/kotlin[]
    // cn.codergege.demo.${convertedProjectName}ApplicationTest
    override var appTestFileName = "${convertedProjectName}ApplicationTest.kt"
    override var codeAppTest = """package $packageName

import $packageName.bean.Greeter
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.core.env.Environment
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
import javax.sql.DataSource

@ExtendWith(SpringExtension::class)
@SpringBootTest
@ActiveProfiles("test")
class SpringCacheApplicationTest {

    @Autowired
    lateinit var greeter: Greeter
    @Autowired
    lateinit var dateSource: DataSource
    @Autowired
    lateinit var env: Environment

    @Autowired
    lateinit var stringRedisTemplate: StringRedisTemplate
    @Autowired
    lateinit var redisTemplate: RedisTemplate<Any, Any>
    @Autowired
    lateinit var jsonRedisTemplate: RedisTemplate<Any, Any>

    /**
     *  Redis 常见五大数据类型:
     *      1) String,
     *      2) List,
     *      3) Set,
     *      4) Hash(散列),
     *      5) ZSet(有序集合)
     *  RedisTemplate 默认使用 jdk 序列化规则
     *  可以用 json 序列化规则
     */
    @Test
    fun testRedis() {
        // String
        stringRedisTemplate.opsForValue().append("msg", "hello")
        val msg = stringRedisTemplate.opsForValue().get("msg")
        println(msg)
        //List
        stringRedisTemplate.opsForList().leftPush("mylist", "1")
        stringRedisTemplate.opsForList().leftPush("mylist", "2")
        stringRedisTemplate.opsForList().leftPushAll("mylist", "3", "4")
        val mylistSize = stringRedisTemplate.opsForList().size("mylist")
        println(mylistSize)

        // 保存对象
        redisTemplate.opsForValue().set("greeter", greeter)
        val g = redisTemplate.opsForValue().get("greeter")
        println(g)

        // json 序列化规则
        jsonRedisTemplate.opsForValue().set("greeter", greeter)
        val g2 = jsonRedisTemplate.opsForValue().get("greeter")
        println("g2: ${'$'}g2")
    }

    @Test
    fun testApp() {
        println("测试...")
        assertTrue(true)
        println(dateSource::class.java)
        println(env.activeProfiles)
    }

    @Test
    fun testGreeter() {
        assertEquals("This is test environment", greeter.greetingMsg)
    }
}
"""
    // cn.codergege.demo.bean.Greeter
    override var beanPackageDir = "$packageDir/bean"
    override var greeterFileName = "Greeter.kt"
    override var codeGreeter = """package $packageName.bean

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import java.io.Serializable

@Component
class Greeter: Serializable {
    // ${'$'} 需要转义, 因为在 kotlin 中 ${'$'} 是字符串占位符
    @Value("\${'$'}{greeting}")
    lateinit var greetingMsg: String
}
"""
    // cn.codergege.demo.controller.HelloControllerTest
    var helloControllerTestFileName = "HelloControllerTest.kt"
    var codeHelloControllerTest = """package $packageName.controller

import $packageName.service.PersonService
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@ExtendWith(SpringExtension::class)
@WebMvcTest(HelloController::class)
@ActiveProfiles("test")
class HelloControllerTest {
    @Autowired
    private lateinit var mvc: MockMvc
    @MockBean
    lateinit var personService: PersonService

    @Test fun hello() {
        this.mvc.perform(get("/hello")
//            .accept(MediaType.TEXT_PLAIN))
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk)
//            .andExpect(content().string("Hello world!"))
    }
}
"""
    // cn.codergege.demo.service.PersonServiceTest
    override var personServiceTestFileName = "PersonServiceTest.kt"
    override var codePersonServiceTest = """package $packageName.service

import $packageName.dao.PersonRepository
import $packageName.entity.Person
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
import java.util.*

@ExtendWith(SpringExtension::class)
@SpringBootTest
@ActiveProfiles("test")
class PersonServiceTest {
    private val log = LoggerFactory.getLogger(this::class.java)
    @Autowired
    lateinit var personService: PersonService
    @Autowired
    lateinit var personRepository: PersonRepository

    @BeforeEach
    fun initH2() {
        val persons = listOf<Person>(
            Person(null,"codergege", 1, Date(), "codergege@163.com"),
            Person(null, "djh", 0, null, null)
        )
        personRepository.saveAll(persons)
    }

    @Test fun testFindPersons() {
        val persons = personService.findPersons("codergege")
        assertEquals(1, persons?.size)
        persons?.forEach(::println)
    }
}
"""
    // end::test/kotlin[]
}